<?php
/**
 * Copyright (c) 2008, Till Klampaeckel
 * 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 *  * Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice, this
 *    list of conditions and the following disclaimer in the documentation and/or
 *    other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * PHP Version 5
 *
 * @category Services
 * @package  Services_Plazes
 * @author   Till Klampaeckel <till@php.net>
 * @license  http://www.opensource.org/licenses/bsd-license.php The BSD License
 * @version  CVS: $Id$
 * @link     http://code.google.com/p/servicesplazes/
 */
 
/**
 * HTTP_Request
 * @ignore
 */
require_once 'HTTP/Request.php';

/**
 * Services_Plazes_Exception
 */
require_once 'Services/Plazes/Exception.php';

/**
 * A class to interface the API of plazes.com.
 *
 * Special thanks go out to plazes for sponsoring work on this package (Thanks,
 * stefan@plazes.com) and especially til@plazes.com for answering all questions.
 *
 * @category Services
 * @package  Services_Plazes
 * @author   Till Klampaeckel <till@php.net>
 * @license  http://www.opensource.org/licenses/bsd-license.php The BSD License
 * @version  Release: @package_version@
 * @link     http://code.google.com/p/servicesplazes/
 * @todo     Allow people to pass proxy settings to HTTP_Request
 * @todo     Implement ATOM feeds as soon as they are available.
 * @todo     Implement MicroFormats as soon as they are available.
 * @todo     Implement multibyte-based checks and conversions to UTF-8.
 * @todo     Implement santiry checks for the response format.
 * @todo     Test, test, test!
 * @todo     Document.
 * @todo     Add useragent.
 */
class Services_Plazes
{
    private $username = '';
    private $password = '';

    private $userAgent = 'Services_Plazes (device: api)';
    
    protected $httpRequest  = null;
    protected $endpoint     = 'https://plazes.com';
    protected $successCodes = array(200, 201);
    
    protected $responseFormat = 'xml';
    
    const ERR_UNKNOWN         = 667;
    const ERR_INTERNAL        = 668;
    const ERR_REQUEST         = 669;
    const ERR_NOT_IMPLEMENTED = 670;
    const ERR_MISSING_PARAM   = 671;
    const ERR_CREATE          = 672;
    const ERR_PARAM           = 673;
    
    /**
     * __construct
     */
    private function __construct()
    {
    }
    
    /**
     * factory
     *
     * @param string $username Plazes user login.
     * @param string $password Plazes user password.
     * @param string $endpoint The API endpoint.
     *
     * @return Services_Plazes
     */
    static function factory($username = '', $password = '', $endpoint = null)
    {
        $plazes = new Services_Plazes;
        $plazes->setAuthInfo($username, $password);
        
        if ($endpoint !== null) {
            $plazes->setEndpoint($endpoint);
        }
        return $plazes;
    }
    
    /**
     * setAuthInfo
     *
     * Used to update the credentials for Basic Auth.
     *
     * @param string $username Plazes user login.
     * @param string $password Plazes user password.
     *
     * @return void
     * @uses   self::_init()
     * @uses   self::$username
     * @uses   self::$password
     */
    public function setAuthInfo($username, $password)
    {
        $this->username = $username;
        $this->password = $password;
        
        $this->_init();
    }
    
    /**
     * setEndpoint
     *
     * Used to set the API endpoint - in case it (ever) changes. Or maybe for
     * staging/sandbox (sandbox.plazes.com) or similar.
     *
     * @param string $endpoint The API endpoint.
     *
     * @return string
     * @throws Services_Plazes_Exception In case no URL is set.
     */
    public function setEndpoint($endpoint)
    {
        $this->endpoint = $endpoint;
        if (empty($this->endpoint)) {
            throw new Services_Plazes_Exception(
                'No endpoint?!',
                self::ERR_INTERNAL
            );
        }
        return $this->endpoint;
    }
    
    /**
     * setParams
     *
     * Populates private class variables from an array. Only used in adapters.
     *
     * This function is used to "set" (or populate) params in an adapter class which
     * are set in Services_Plazes.
     *
     * @param array $params An associative array of parameters.
     *
     * @return void
     */
    public function setParams($params)
    {
        foreach ($params AS $key=>$value) {
            $this->$key = $value;
        }
    }
    
    /**
     * _init
     *
     * Sets up a HTTP_Request object for internal use in all adapters.
     *
     * This function is called each time the credentials for Basic Auth
     * are set/updated to reflect a change on the HTTP_Request object
     * which is used to perform all requests.
     *
     * @uses self::$username
     * @uses self::$password
     * @uses self::$httpRequest
     * @uses HTTP_Request
     *
     * @return boolean
     * @throws Services_Plazes_Exception In case HTTP_Request fails.
     *
     * @see self::setAuthInfo()
     */
    private function _init()
    {
        $this->httpRequest = new HTTP_Request;
        if (PEAR::isError($this->httpRequest)) {
            throw new Services_Plazes_Exception(
                'Failed to initialize HTTP_Request',
                self::ERR_INTERNAL
            );
        }
        
        if (!empty($this->username) && !empty($this->password)) {
            $this->httpRequest->setBasicAuth($this->username, $this->password);
        }
        $this->httpRequest->addHeader('Content-Type', 'application/xml');
        $this->httpRequest->addHeader('User-Agent', $this->userAgent);

        return true;
    }
    
    /**
     * __get
     *
     * Called via magic methods.
     *
     * @param string $method The adapter.
     *
     * @return object
     * @throws Services_Plazes_Exception In case of an unknown method.
     */
    public function __get($method)
    {
        $method  = strtolower($method);
        $adapter = '';
        
        switch ($method) {
        case 'activities':
        case 'plazes':
        case 'sessions':
        case 'users':
            $adapter .= ucfirst($method);
            break;
            
        default:
            throw new Services_Plazes_Exception(
                'Unknown adapter called.',
                self::ERR_UNKNOWN
            );
        }
        
        $className = 'Services_Plazes_Adapter_' . $adapter;

        if (!class_exists($className)) {
            include_once str_replace('_', '/', $className) . '.php';
        }
        
        $params                   = array();
        $params['httpRequest']    = $this->httpRequest;
        $params['endpoint']       = $this->endpoint;
        $params['responseFormat'] = $this->responseFormat;

        return call_user_func(array($className, 'factory'), $params);
    }
    
    /**
     * __set
     *
     * @param string $var   The private/protected variable to set.
     * @param mixed  $value The value! ;-)
     *
     * @return void
     */
    public function __set($var, $value)
    {
        $this->$var = $value;
    }

    /**
     * makeRequest
     *
     * @param string $url    The complete URL to send the request to.
     * @param string $method The REQUEST_METHOD.
     * @param string $data   The data to send along with the request.
     *
     * @return string
     * @throws Services_Plazes_Adapter_Exception In case the request fails.
     * @uses   self::$httpRequest
     * @todo   Use HTTP_Request constants for request methods.
     */
    protected function makeRequest($url, $method = 'GET', $data = null)
    {
        $method = strtoupper($method);
        
        $this->httpRequest->setUrl($url);
        $this->httpRequest->setMethod($method);
        
        if ($data !== null && in_array($method, array('POST', 'PUT'))) {
            $this->httpRequest->setBody($data);
        }
        if ($method == 'POST' && empty($data)) {
            $this->httpRequest->addHeader('Content-Length', 0);
        }
        $response = $this->httpRequest->sendRequest();
        if (PEAR::isError($response)) {

            // Re-cast PEAR_Error to an Exception
            $msg  = $response->getMessage();
            $code = $response->getCode();

            throw new Services_Plazes_Adapter_Exception(
                'Error making request: ' . $msg . '(Code: ' . $code . ')',
                self::ERR_REQUEST
            );
        }

        if (!in_array($this->httpRequest->getResponseCode(), $this->successCodes)) {

            $errors = $this->parseErrors($this->httpRequest->getResponseBody());
            throw new Services_Plazes_Adapter_Exception(
                'Request/operation failed: ' . implode("\n", $errors),
                $this->httpRequest->getResponseCode()
            );
        }
        return $this->httpRequest->getResponseBody();
    }
    
    /**
     * setProxy
     *
     * Pass proxy information to the HTTP_Request object.
     *
     * @param string $host     The host of the proxy.
     * @param int    $port     The port of the proxy.
     * @param string $username The proxy username if authorization is needed.
     * @param string $password The proxy password if authorization is needed.
     *
     * @return void
     */
    public function setProxy($host, $port = 8080, $username = null,
        $password = null)
    {
        $this->httpRequest->setProxy($host, $port, $username, $password);
    }
    
    /**
     * parseErrors
     *
     * Parses the <errors> XML into a more readable format (arrays).
     *
     * @param string $response The response received from plazes API.
     *
     * @return array
     * @todo   Test.
     */
    protected function parseErrors($response)
    {
        $errors = array();
        if (empty($response)) {
            array_push($errors, 'Unknown error.');
            return $errors;
        }

        $simpleXml = @simplexml_load_string($response);
        if ($simpleXml === false) {
            $msg = 'Unknown error (probably mal-formed format (not xml)).';
            array_push($errors, $msg);

            $msg = "Response received: $response";
            array_push($errors, $msg);

            return $errors;
        }

        for ($x=0; $x<count($simpleXml->error); $x++) {
            array_push($errors, $simpleXml->error[$x]);
        }
        return $errors;
    }
    
    /**
     * _parseResponse
     *
     * To be overwritten in the adapters.
     *
     * @param string $response The response.
     *
     * @return void
     */
    private function _parseResponse($response)
    {
    }
}
?>
